// ==UserScript==
// @name         Gitcode PR public helper
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Process Gitcode PR for OHOS developers
// @author       Petrov Igor
// @match        https://gitcode.com/*/pull/*
// @icon         https://cdn-static.gitcode.com/static/images/logo-favicon.png
// @updateURL    https://raw.gitcode.com/ipetrov/my_little_scripts/raw/main/userscripts/gitcode/pr.user.js
// @downloadURL  https://raw.gitcode.com/ipetrov/my_little_scripts/raw/main/userscripts/gitcode/pr.user.js
// @grant        none
// ==/UserScript==

const HIDE_SHOW_BUTTON_ID = "gitcode-hide-show-ci-bot-button-id";
const HIDE_BUTTON_STRING = "Hide excessive comments";
const SHOW_BUTTON_STRING = "Show excessive comments";

// TODO(ipetrov): workaround, need to make observer
let currentToolbarIndex = -1;

function getCommentsElement() {
    return document.getElementsByClassName("flex flex-col gap-[32px]")[0];
}

function getToolbarElement() {
    return document.getElementsByClassName("devui-tabs__nav devui-tabs__nav--tabs devui-tabs__nav--top")[0];
}

function getHideShowButton() {
    return document.getElementById(HIDE_SHOW_BUTTON_ID);
}

function getChangesHeaderElement() {
    return document.getElementsByClassName("flex px-5 justify-between items-center max-md:items-start")[0];
}

function waitLoadCommentsAndProcess(processComments) {
    const commentsElement = getCommentsElement();
    let commentsCount = commentsElement.children.length;
    const commentCheckIntervalID = setInterval(function() {
        const commentsElement = getCommentsElement();
        if (!commentsElement) {
            return;
        }
        const currentCommentsCount = commentsElement.children.length;
        if (currentCommentsCount == 0) {
            return;
        }
        // TODO(ipetrov): Add skip count?
        if (currentCommentsCount != commentsCount) {
            clearInterval(commentCheckIntervalID);
            // Add delay to load comments content
            setTimeout(function() {
                processComments(commentsElement);
            }, 1000);
        }
    }, 500);
}

function changeHideShowCommentsStatus(btn) {
    if (btn.innerText == HIDE_BUTTON_STRING) {
        btn.innerText = SHOW_BUTTON_STRING;
        btn.style.backgroundColor = 'grey';
        return HIDE_BUTTON_STRING;
    }
    btn.innerText = HIDE_BUTTON_STRING;
    btn.style.backgroundColor = 'green';
    return SHOW_BUTTON_STRING;
}

function getCurrentVisibilityStatus(currentButtonStatus) {
    return (currentButtonStatus == HIDE_BUTTON_STRING) ? SHOW_BUTTON_STRING : HIDE_BUTTON_STRING; 
}

function addHideShowButton(elementBeforeButton) {
    const hideShowButton = document.createElement("button");
    hideShowButton.class = "gitcode-hide-show-ci-bot-button-class";
    hideShowButton.id = HIDE_SHOW_BUTTON_ID;
    hideShowButton.style = 
        `border-width: 0;
        padding-top: 0.5rem;
        padding-bottom: 0.5rem;
        padding-left: 0.75rem;
        padding-right: 0.75rem;
        background-color: green;
        line-height: 1rem;
        fill: currentColor;
        box-shadow: inset 0 0 0 1px #bfbfbf;
        justify-content: center;
        align-items: center;
        font-size: 0.875rem;
        border-radius: 0.25rem;
        text-decoration: none;
        color: rgb(255 255 255)`
    changeHideShowCommentsStatus(hideShowButton);
    hideShowButton.onmousedown = () => {
        hideShowButton.style.transform = 'scale(0.95)';
        hideShowButton.style.transition = 'transform 0.1s ease-in-out';
    }
    hideShowButton.onmouseup = () => {
        hideShowButton.style.transform = 'scale(1.0)';
        hideShowButton.style.transition = 'transform 0.1s ease-in-out';
    }
    hideShowButton.onclick = () => {
        const commentsElement = getCommentsElement();
        if (!commentsElement) {
            return;
        }
        const btn = getHideShowButton();
        const requiredVisibility = changeHideShowCommentsStatus(btn);
        setTimeout(() => {
            changeCIBotCommentsVisibility(commentsElement, requiredVisibility);
        }, 10);
    };
    elementBeforeButton.after(hideShowButton);
    return hideShowButton;
}

function getFileChangesCollection() {
    return document.getElementsByClassName("comment-container bg-[var(--theme-default-bg)] rounded-t-[4px] rounded-b relative diff-view");
}

function expandAllChanges() {
    const fileChanges = getFileChangesCollection();
    // Expand changes for all changes
    for (const fileChangeElement of fileChanges) {
        const cursorPointerElement = fileChangeElement.getElementsByClassName("devui-icon__container devui-icon--no-slots cursor-pointer")[0];
        if (!cursorPointerElement) {
            continue;
        }
        if (cursorPointerElement.children.length == 0) {
            continue;
        }
        // Expand changes for file
        if (cursorPointerElement.children[0].className == "icon icon-chevron-down cursor-pointer") {
            cursorPointerElement.children[0].click();
        }
    }
    // Load diffs for all files
    for (const fileChangeElement of fileChanges) {
        const loadChangesElement = fileChangeElement.getElementsByClassName("cursor-pointer text-primary")[0];
        if (!loadChangesElement) {
            continue;
        }
        // Load diffs for file
        loadChangesElement.click();
    }
}

function createExpandAllChangesButton() {
    const expandAllChangesButton = document.createElement("button");
    expandAllChangesButton.className = "gitcode-expand-all-changes-ci-bot-button-class";
    expandAllChangesButton.id = "gitcode-expand-all-changes-ci-bot-button-id";
    expandAllChangesButton.innerText = "Show all changes";
    expandAllChangesButton.title = "Expand all changes and load diffs"
    expandAllChangesButton.style =
        `border-width: 0;
        padding-top: 0.5rem;
        padding-bottom: 0.5rem;
        padding-left: 0.75rem;
        padding-right: 0.75rem;
        background-color: white;
        line-height: 1rem;
        fill: currentColor;
        box-shadow: inset 0 0 0 1px #bfbfbf;
        justify-content: center;
        align-items: center;
        font-size: 0.875rem;
        border-radius: 0.25rem;
        text-decoration: none;
        color: rgb(0 0 0)`
    expandAllChangesButton.onmousedown = () => {
        expandAllChangesButton.style.transform = 'scale(0.95)';
        expandAllChangesButton.style.transition = 'transform 0.1s ease-in-out';
    }
    expandAllChangesButton.onmouseup = () => {
        expandAllChangesButton.style.transform = 'scale(1.0)';
        expandAllChangesButton.style.transition = 'transform 0.1s ease-in-out';
    }
    expandAllChangesButton.onmouseenter = () => {
        expandAllChangesButton.style.backgroundColor = "rgb(245 245 245)"
    }
    expandAllChangesButton.onmouseleave = () => {
        expandAllChangesButton.style.backgroundColor = "white"
    }
    expandAllChangesButton.onclick = () => {
        setTimeout(() => {
            expandAllChanges();
        }, 10);
    };
    return expandAllChangesButton;
}

function addDiffNumbersToChangesTree(changesTreeElement) {
    const fileChanges = getFileChangesCollection();
    const fileToDiff = new Map();
    for (const fileChangeElement of fileChanges) {
        const fileChangeHeaderElement = fileChangeElement.getElementsByClassName("flex gap-4 h-full items-center relative diff-item-text")[0];
        if (!fileChangeHeaderElement) {
            continue;
        }
        const filePathElement = fileChangeHeaderElement.getElementsByClassName("text-[var(--theme-text)] flex items-center flex-1 min-w-0")[0];
        if (!filePathElement) {
            continue;
        }
        fileChangeElement.sty
        const clonedDiffElement = document.createElement("div")
                                  .appendChild(fileChangeHeaderElement.children[fileChangeHeaderElement.children.length - 1]
                                               .cloneNode(true));
        clonedDiffElement.style.padding = '5px';
        fileToDiff.set(filePathElement.title, clonedDiffElement);
    }
    const changesNodes = changesTreeElement.getElementsByClassName("devui-tree__node-content--value-wrapper");
    for (const changesNode of changesNodes) {
        changesNode.parentElement.onclick = function() {
            setTimeout(function() {
                addDiffNumbersToChangesTree(changesTreeElement);
            }, 100);
        };
        if (changesNode.children.length == 0 || changesNode.parentElement.children.length != 2) {
            continue;
        }
        if (fileToDiff.has(changesNode.children[0].title)) {
            changesNode.after(fileToDiff.get(changesNode.children[0].title));
        }
    }
}

function changeCIBotCommentsVisibility(commentsElement, visibilityRequest) {
    let comments = commentsElement.children;
    let isLastGGWComment = true;
    // TODO(ipetrov): Solve dinamic comment content loading issue
    // const OHOS_BUILD_TRIGGER_STRING = '预计在60分钟内完成,门禁结果会同步发送到';
    // let isLastBuildTriggerComment = true;
    // const OHOS_BUILD_TABLE_STRING = '您可以通过如下链接查看门禁报告';
    // let isLastBuildTableComment = true;
    const SKIP_OHOS_CI_BOT_COMMENTS_COUNT = 8;
    let skippedOHOSCIBotCommentsCount = 0;
    Array.from(comments).reverse().forEach(function (item, index) {
        const commentUserElement = item.getElementsByClassName("g-link g-link-router-link user-link")[0];
        // Some non-comment element, skip it
        if (!commentUserElement) {
            return;
        }
        if (commentUserElement.textContent == "ggwatcher") {
            if (visibilityRequest == SHOW_BUTTON_STRING) {
                item.style.display = '';
                return;
            }
            // Show only last ggwatcher bot comment
            if (isLastGGWComment) {
                isLastGGWComment = false;
            } else {
                item.style.display = 'none';
            }
        } else if (commentUserElement.textContent == "openharmony_ci") {
            if (visibilityRequest == SHOW_BUTTON_STRING) {
                item.style.display = '';
                return;
            }
            // TODO(ipetrov): Solve dinamic comment content loading issue
            // if (item.innerHTML.includes(OHOS_BUILD_TRIGGER_STRING) && isLastBuildTriggerComment) {
            //     isLastBuildTriggerComment = false;
            // } else if (item.innerHTML.includes(OHOS_BUILD_TABLE_STRING) && isLastBuildTableComment) {
            //     isLastBuildTableComment = false;
            // } 
            if (item.className == "relative g-content-card-v2 bottom-line" && skippedOHOSCIBotCommentsCount < SKIP_OHOS_CI_BOT_COMMENTS_COUNT) {
                skippedOHOSCIBotCommentsCount++;
            } else {
                item.style.display = 'none';
            }
        }
    });
}

function addCallbackForSeeMoreOnClick(commentsElement) {
    const seeMoreButton = commentsElement.getElementsByClassName("collapse-btn__more")[0];
    if (!seeMoreButton) {
        return false;
    }
    seeMoreButton.onclick = () => {
        waitLoadCommentsAndProcess(function(commentsElement) {
            addCallbackForSeeMoreOnClick(commentsElement);
            changeCIBotCommentsVisibility(commentsElement, getCurrentVisibilityStatus(getHideShowButton().innerHTML));
        });
    };
    return true;
}

function getDate(item) {
    if (!item) {
        return undefined;
    }
    let commentHeader = item.getElementsByClassName("g-discussion-header-info")[0];
    if (!commentHeader) {
        return undefined;
    }
    let timeHeader = commentHeader.getElementsByClassName("g-fold-ellipsis event-msg-wrap")[0];
    if (!timeHeader) {
        return undefined;
    }
    return new Date(timeHeader.title.replace(' ', 'T'));
}

function sortAllComments(commentsElement) {
    const arr = Array.from(commentsElement.children);
    arr.sort(function(lhs, rhs) {
        const lhsDate = getDate(lhs);
        const rhsDate = getDate(rhs);
        if (lhsDate === undefined || rhsDate === undefined) {
            return 0;
        } 
        return rhsDate - lhsDate;
    })
    .forEach(child => commentsElement.appendChild(child));
}

function waitElementAndProcess(getElementCallback, elementProcessor, timeout) {
    const elementIntervalID = setInterval(function() {
        const element = getElementCallback();
        if (!element) {
            return;
        }
        clearInterval(elementIntervalID);
        elementProcessor(element);
    }, timeout);
}

function isToolbarChildActive(toolbarChildElement) {
    return toolbarChildElement.className == "active";
}

// OHOS CI Buttons

function getPRAuthor() {
    return document.getElementsByClassName("text-[var(--devui-aide-text)] font-[600]")[1].innerText;
}

function getPRStatusElement() {
    const prStatusSubElement = document.getElementsByClassName("flex flex-col gap-y-[16px]")[0];
    if (!prStatusSubElement) {
        return undefined;
    }
    return prStatusSubElement.parentElement;
}

function createCIActionButton(cmd, text) {
    const btn = document.createElement("button");
    btn.innerText = text;
    btn.style.background = '#F0F0F0';
    btn.style.borderRadius = "8px";
    btn.style.padding = "2px";
    btn.onclick = () => {};
    return btn;
}

function createCIActionsList(actionsName, buttonsList) {
    const actionsListElement = document.createElement("div");
    const actionsClassPart = actionsName.replace(' ', '-').toLowerCase();
    actionsListElement.className = actionsClassPart + "actions-class";
    const headerElement = document.createElement("h3");
    headerElement.style.fontWeight = 'bold'
    headerElement.append(document.createTextNode(actionsName));
    const actionsList = document.createElement("ul");
    actionsList.style.display = "flex";
    for (const btn of buttonsList) {
        const actionElement = document.createElement("li");
        actionElement.style.padding = "3px";
        actionElement.appendChild(btn);
        actionsList.appendChild(actionElement);
    }
    actionsListElement.appendChild(headerElement);
    actionsListElement.appendChild(actionsList);
    return actionsListElement;
}

function createCIActionsElement() {
    let ciActionDiv = document.createElement("div");
    ciActionDiv.className = "custom-ci-actions-class";
    const arr = new Array();
    arr.push(createCIActionButton("", "Start build"));
    arr.push(createCIActionButton("", "Stop build"));
    ciActionDiv.appendChild(createCIActionsList("OHOS CI", arr));
    return ciActionDiv;
}

function processDisscussion() {
    currentToolbarIndex = 0;
    // TODO(ipetrov): Seems can be removed
    const hideShowButton = getHideShowButton();
    if (hideShowButton) {
        hideShowButton.parentElement.removeChild(hideShowButton);
    }
    addHideShowButton(getToolbarElement().parentElement);
    // TODO(ipetrov): Gitcode elements do not dispatch events
    // waitElementAndProcess(getPRStatusElement, function(prStatusElement) {
    //     prStatusElement.before(createCIActionsElement());
    // }, 100);
    waitElementAndProcess(getCommentsElement, function(commentsElement) {
        waitLoadCommentsAndProcess(function(commentsElement) {
            addCallbackForSeeMoreOnClick(commentsElement);
            const btn = getHideShowButton();
            if (btn.innerText == HIDE_BUTTON_STRING) {
                btn.click();
            }
        });
    }, 1000);
}

function removeHideShowButtonIfExisted() {
    const hideShowButton = getHideShowButton();
    if (hideShowButton) {
        hideShowButton.parentElement.removeChild(hideShowButton);
    }
}

function processCommits() {
    currentToolbarIndex = 1;
}

function processChecks() {
    currentToolbarIndex = 2;
}

function processDiffs() {
    currentToolbarIndex = 3;
    waitElementAndProcess(getChangesHeaderElement, function(changesHeaderElement) {
        const beforeButtonElement = changesHeaderElement.getElementsByClassName("custom-icon-container")[0];
        const border = changesHeaderElement.getElementsByClassName("w-px h-5 bg-[var(--theme-line-border)] mr-3 ml-1")[0].cloneNode(true);
        const btn = createExpandAllChangesButton();
        beforeButtonElement.before(btn);
        beforeButtonElement.before(border);
        // Add diffs number info to changes tree
        waitElementAndProcess(function() { return document.getElementsByClassName("devui-tree mt-2")[0]; },
        function(changesTreeElement) {
            addDiffNumbersToChangesTree(changesTreeElement);
        }, 1000);
    }, 1000);
}

(function () {
    'use strict';
    // PR content is loaded dynamically, so wait until the required element is loaded
    waitElementAndProcess(getToolbarElement, function(toolbarElement) {
        // TODO(ipetrov): avoid onclick and add observer?
        // Discussion
        const discussionElement = toolbarElement.children[0];
        discussionElement.onclick = function() {
            if (currentToolbarIndex != 0) {
                processDisscussion();
            }
        };
        // Commits
        toolbarElement.children[1].onclick = function() {
            if (currentToolbarIndex == 1) {
                return;
            }
            removeHideShowButtonIfExisted();
            processCommits();
        };
        // Checks
        toolbarElement.children[2].onclick = function() {
            if (currentToolbarIndex == 2) {
                return;
            }
            removeHideShowButtonIfExisted();
            processChecks();
        };
        // Diffs
        const diffsElement = toolbarElement.children[3];
        diffsElement.onclick = function() {
            if (currentToolbarIndex == 3) {
                return;
            }
            removeHideShowButtonIfExisted();
            processDiffs();
        }
        // Process on start of page loading
        if (isToolbarChildActive(discussionElement)) {
            processDisscussion();
        } else if (isToolbarChildActive(diffsElement)) {
            processDiffs();
        }
    }, 500);
})();
